﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using NUnit.Framework;
using UnitTestUtils.UI;

namespace Common.Tests.System.Threading.Tasks
{
    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestFixture]
    public class ProgressNotifierOfTTests
    {
        readonly List<int> _threadOfExecution = new List<int>();

        [SetUp]
        public void Initialize()
        {
            _threadOfExecution.Clear();
        }

        [TearDown]
        public void Dispose()
        {
        }

        [Test]
        public void ProgressOnStandardThread()
        {
            var notifier = new ProgressNotifier<int[]>();
            notifier.ProgressHandler = (chunk, progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {2} - {1}", progressPercentage, string.Join<int>(", ", chunk), threadID);
                _threadOfExecution.Add(threadID);
            };

            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var resultTask = Task.Factory.StartNew<int[]>((progrssTarget) =>
            {
                var pn = (ProgressNotifier<int[]>)progrssTarget;
                var result = new List<int>();
                for (int i = 0; i < 10; i++)
                {
                    result.Add(i);
                    pn.Notify(new int[] { i }, i * 10);
                }
                return result.ToArray();
            }, notifier);

            var ret = SpinWait.SpinUntil(() => ((int)resultTask.Status > 4), TimeSpan.FromSeconds(5));

            Assert.IsTrue(ret);
            Assert.AreEqual(10, resultTask.Result.Length);
        }

        [Test]
        public void ProgressOnStandardThread_cleanedup()
        {
            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var resultTask = Task.Factory.StartNewWithProgress<int[]>((pn) =>
            //
            {
                var result = new List<int>();
                for (int i = 0; i < 10; i++)
                {
                    result.Add(i);
                    pn.Notify(new int[] { i }, i * 10);
                    pn.CancellationToken.ThrowIfCancellationRequested();
                }
                return result.ToArray();
            },
                // Progress Handling
            (chunk, progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {2} - {1}", progressPercentage, string.Join<int>(", ", chunk), threadID);
                _threadOfExecution.Add(threadID);
            }, new CancellationToken());

            var ret = SpinWait.SpinUntil(() => ((int)resultTask.Status > 4), TimeSpan.FromSeconds(5));

            Assert.IsTrue(ret);
            Assert.AreEqual(10, resultTask.Result.Length);
        }

        volatile bool IsLoopExecuting = false;

        [Test]
        public void ProgressOnStandardThread_Cancellation()
        {
            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var tokenSource = new CancellationTokenSource();
            var resultTask = Task.Factory.StartNewWithProgress<int[]>((pn) =>
            //
            {
                var result = new List<int>();
                for (int i = 0; i < 100000; i++)
                {
                    IsLoopExecuting = true;
                    result.Add(i);

                    pn.CancellationToken.ThrowIfCancellationRequested();
                    pn.Notify(new int[] { i }, i * 10);
                }
                return result.ToArray();
            },
                // Progress Handling
            (chunk, progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {2} - {1}", progressPercentage, string.Join<int>(", ", chunk), threadID);
                _threadOfExecution.Add(threadID);
            }, tokenSource.Token);

            SpinWait.SpinUntil(() => IsLoopExecuting);

            // now try to cancel
            tokenSource.Cancel();

            try
            {
                resultTask.Wait();
            }
            catch (Exception)
            {
            }

            Assert.IsTrue(resultTask.Exception.InnerException is OperationCanceledException);
        }

        volatile bool executed = false;

        [Test]
        public void ProgressOnDispatcherThread()
        {
            executed = false;

            Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() =>
            {
                ProgressOnStandardThread();
                executed = true;
            }));

            Dispatcher.CurrentDispatcher.WaitOne(() => executed);

            int expectedThread = Dispatcher.CurrentDispatcher.Thread.ManagedThreadId;
            Assert.IsTrue(_threadOfExecution.All(v => v == expectedThread));
        }
    }

    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>
    [TestFixture]
    public class ProgressNotifierTests
    {
        readonly List<int> _threadOfExecution = new List<int>();

        [SetUp]
        public void Initialize()
        {
            _threadOfExecution.Clear();
        }

        [TearDown]
        public void Dispose()
        {
        }

        [Test]
        public void ProgressOnStandardThread()
        {
            var notifier = new ProgressNotifier();
            notifier.ProgressHandler = (progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {1}", progressPercentage, threadID);
                _threadOfExecution.Add(threadID);
            };

            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var resultTask = Task.Factory.StartNew((progrssTarget) =>
            {
                var pn = (ProgressNotifier)progrssTarget;
                var result = new List<int>();
                for (int i = 0; i < 10; i++)
                {
                    result.Add(i);
                    pn.Notify(i * 10);
                }
            }, notifier);

            var ret = SpinWait.SpinUntil(() => ((int)resultTask.Status > 4), TimeSpan.FromSeconds(5));

            Assert.IsTrue(ret);
        }

        [Test]
        public void ProgressOnStandardThread_cleanedup()
        {
            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var resultTask = Task.Factory.StartNewWithProgress((pn) =>
            //
            {
                var result = new List<int>();
                for (int i = 0; i < 10; i++)
                {
                    result.Add(i);
                    pn.Notify(i * 10);
                    pn.CancellationToken.ThrowIfCancellationRequested();
                }
            },
                // Progress Handling
            (progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {2} - {1}", threadID);
                _threadOfExecution.Add(threadID);
            }, new CancellationToken());

            var ret = SpinWait.SpinUntil(() => ((int)resultTask.Status > 4), TimeSpan.FromSeconds(5));

            Assert.IsTrue(ret);
        }

        volatile bool IsLoopExecuting = false;

        [Test]
        public void ProgressOnStandardThread_Cancellation()
        {
            Console.WriteLine("Started on thread id:  {0}", Thread.CurrentThread.ManagedThreadId);

            var tokenSource = new CancellationTokenSource();
            var resultTask = Task.Factory.StartNewWithProgress((pn) =>
            //
            {
                var result = new List<int>();
                for (int i = 0; i < 100000; i++)
                {
                    IsLoopExecuting = true;
                    result.Add(i);

                    pn.CancellationToken.ThrowIfCancellationRequested();
                    pn.Notify(i * 10);
                }
            },
                // Progress Handling
            (progressPercentage) =>
            {
                var threadID = Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("{0} - {1}", progressPercentage, threadID);
                _threadOfExecution.Add(threadID);
            }, tokenSource.Token);

            SpinWait.SpinUntil(() => IsLoopExecuting);

            // now try to cancel
            tokenSource.Cancel();

            try
            {
                resultTask.Wait();
            }
            catch (Exception)
            {
            }

            Assert.IsTrue(resultTask.Exception.InnerException is OperationCanceledException);
        }
    }
}